Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert
- entities.geojson — Schulen, deren Geokoordinaten und Attribute: entity_id, capacity, und andere Attribute
- entities.csv — Statistische Blöcke Berlins und optimierungsrelevante Attribute: entity_id, capacity
- units.geojson — Statistische Blöcke Berlins, deren Geometrie und Attribute
- units.csv — Statsitische Blöcke Berlins und optimierungsrelevante Attribute: unit_id, population, percentage_sgb
- weights.csv — optimierungsrelevante Gewichte wie Fußwege / Spalten: entity_id, unit_id, weight, value
- assignment.csv — eine initiale Zuordnung / Spalten: unit_id, entity_id
Bereits in anderen scripten wurde vorbereitet:
- Fußwegen von einer großen Sichprobe von (Wohn-)Gebäuden zu allen Schulen wurden berechnet und in
route_matrix.csv gespeichert
- Die Stichprobe wurde in
sampled_buildings.csv gespeichert
Die Daten werden wiefolgt vorbereitet:
- pro Block werden die Anzahl der einzuschulenden Kinder mit Hilfe der Einwohnerzahlen nach Alter auf LOR-Ebene in
EWR201512E_Matrix.csv hochgerechnet
- Kinder des LOR werden Anteilig nach Einwohnerzahl des Blocks im Verhältnis zum LOR auf die Blöcke verteilt
- es werden minimale, durchschnittliche und maximale Fußwege aus jedem Block errechnet
TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)
Laden der Daten
sampled_buildings = read_rds('output/sampled_buildings.rds')
bez = readOGR('download/RBS_OD_BEZ_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/RBS_OD_BEZ_2015_12.geojson", layer: "OGRGeoJSON"
with 13 features
It has 2 fields
blk = readOGR('download/RBS_OD_BLK_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/RBS_OD_BLK_2015_12.geojson", layer: "OGRGeoJSON"
with 15720 features
It has 4 fields
lor = readOGR('download/RBS_OD_LOR_2015_12.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/RBS_OD_LOR_2015_12.geojson", layer: "OGRGeoJSON"
with 447 features
It has 8 fields
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields
Schulwege
route_matrix = read_rds('output/route_matrix.rds')
Schulkapazitäten und Einwohnerzahler auf LOR-Ebene
kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
Parsed with column specification:
cols(
Bezirk = col_character(),
Schulnummer = col_character(),
Schulname = col_character(),
Plätze = col_integer(),
Anmeldungen = col_character()
)
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Parsed with column specification:
cols(
.default = col_character(),
ZEIT = col_integer(),
STADTRAUM = col_integer(),
E_E = col_number(),
E_EM = col_number(),
E_EW = col_number(),
E_E50_55 = col_number(),
E_E25U55 = col_number(),
E_E55U65 = col_number(),
E_E65U80 = col_number()
)
See spec(...) for full column specifications.
Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten
re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2)
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"
Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?
re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"
Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:
bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
[1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12" "07G13" "07G14" "07G15" "07G16" "07G17"
[14] "07G18" "07G19" "07G20" "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28" "07G29" "07G30"
[27] "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))
Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.
data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):
relevant_schools = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
[1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12" "07G13" "07G14" "07G15" "07G16" "07G17"
[14] "07G18" "07G19" "07G20" "07G21" "07G22" "07G23" "07G24" "07G25" "07G26" "07G27" "07G28" "07G29" "07G30"
[27] "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
Mapping Bezirk->LOR->Block
df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)
Sanity-Check: LORs und Blöcke im Bezirk
bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Kinder im Bezirk auf Blöcke hochrechnen
Über die Einwohnerinformationen in RBS_OD_BLK_2015_12.geojson kann EWR201512E_Matrix.csv von LOR-Ebene auf Blockebene hochgerechnet werden.
TODO: Stattdessen mit https://github.com/berlinermorgenpost/cogran machen?
Plot der 6-Jährigen nach EWR201512E_Matrix.csv
Wir verwenden das mittel der 5- und 6-Jährigen.
TODO neue Daten von Torres? TODO Prognose?
relevant_ewr = einwohner_lor %>%
select(RAUMID, E_E05_06, E_E06_07) %>%
filter(RAUMID %in% relevant_lors$PLR) %>%
# Schnitt der 5 und 6-Jährigen
mutate(kids=(as.numeric(gsub(',','.',E_E06_07))+as.numeric(gsub(',','.',E_E05_06)))/2) %>% as.data.frame()
data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Plot der Einwohner auf Blockebene
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(df_blk, by=c('id'='BLK')) %>% mutate(Einw=ifelse(Einw==0, NA, Einw))
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=Einw), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Hochrrechnung auf Blöcke, Strukturquote
Strukturquote = 0.9
kids_in_blks = relevant_blks %>%
group_by(PLR) %>%
mutate(EinwRatio = Einw/sum(Einw)) %>%
ungroup %>%
left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>%
mutate(kids = EinwRatio*kids) %>%
mutate(kids = Strukturquote*kids) %>% # Strukturquote
select(BEZ, PLR, BLK, Einw, kids) %>%
as.data.frame()
row.names(kids_in_blks) = kids_in_blks$BLK
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Verfügbare Plätze
relevant_kapas = kapas %>% select(Schulnummer, Kapa=`Plätze`) %>% filter(Schulnummer %in% relevant_schools) %>% as.data.frame()
#row.names(relevant_kapas) = relevant_kapas$Schulnummer
Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken
'Summe Kapas'
[1] "Summe Kapas"
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
kids_in_blks$kids %>% sum
[1] 2620.8
relevant_ewr$kids %>% sum
[1] 2912
Schulwege von Blöcken zu Schulen aggregieren
Für jedes Wohngebäude suchen wir den zugehörigen Block
residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
left_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
head(routes_from_blks)
Plot der relevanten Blöcke (mit Wohngebäuden)
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
#geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks
Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1012 33
travel_matrix
Sozioökonomische Daten
Wir haben Sozioökonomische Daten in den Wahlbezirken. Strategie: - Schneiden der Wahlbezirke mit den Blöcken - Übernahme des Prozentwertes vom Wahlbezirk für jeden (Unter-)block - Zuordnung zu jedem Block und Vereinigung durch Flächen/Wohnhaus-gewichtetes Mittel des Prozentwertes
UWB = readOGR('download/RBS_OD_UWB_AGH2016.geojson', layer = 'OGRGeoJSON', stringsAsFactors = FALSE)
OGR data source with driver: GeoJSON
Source: "download/RBS_OD_UWB_AGH2016.geojson", layer: "OGRGeoJSON"
with 1779 features
It has 4 fields
UWB$ID = paste0(UWB$BEZ, UWB$UWB)
sozio_UWB = read_excel('download/DL_BE_AH2016_Strukturdaten.xlsx', sheet = 3) %>% select(ID, sgbIIu65=`Einwohner unter 65 in SGB II 2014 Prozent`)
UWB = UWB %>% sp::merge(sozio_UWB, by='ID')
ggplot(broom::tidy(UWB, region='ID') %>% inner_join(UWB %>% as.data.frame, by=c('id'='ID'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Check for self intersections
wgs84 = CRS(proj4string(blk))
ea_projection = CRS("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
blk_in_bez = blk[blk$BEZ == '07',]
gIsValid(blk_in_bez)
[1] TRUE
blk_in_bez = gBuffer(spTransform(blk_in_bez, ea_projection), byid=T, width = -0.1)
any(xor(gIntersects(buffered_blk_in_bez, byid=T, returnDense = T), diag(1, length(blk_in_bez)) == 1))
[1] FALSE
plot(blk_in_bez)

#gIntersects(UWB[UWB$BEZ == '07',], byid=TRUE)
uwb_in_bez = gBuffer(spTransform(UWB[UWB$BEZ == '07',], ea_projection), byid=T, width=-0.1)
gIsValid(uwb_in_bez)
[1] TRUE
any(xor(gIntersects(uwb_in_bez, byid=T, returnDense = T), diag(1, length(uwb_in_bez)) == 1))
[1] FALSE
plot(uwb_in_bez)

intersection = gIntersection(uwb_in_bez, blk_in_bez, byid = T, drop_lower_td = T)
gIsValid(intersection)
[1] TRUE
plot(intersection)

intersection_uwb_data = intersection %>% over(uwb_in_bez)
intersection_blk_data = intersection %>% over(blk_in_bez)
intersection_area = gArea(intersection, byid=T)
intersection_data = cbind(
intersection_uwb_data %>% select(UWB_ID=ID, sgbIIu65),
intersection_blk_data %>% select(BLK, BLK_Einw=Einw),
data.frame(area=intersection_area)
)
length(intersection)
[1] 1219
nrow(uwb_in_bez)
[1] 123
nrow(blk_in_bez)
[1] 1201
# did we miss any blocks in
setdiff(blk_in_bez$BLK, intersection_data$BLK)
character(0)
Pro Block mische die SGB-Werte der Unterblöcke gewichtet nach Fläche. FIXME - besser nach Anzahl der Wohnhäuser?
sgbII_blk = intersection_data %>%
group_by(BLK) %>%
summarise(sgbIIu65=sum(sgbIIu65*area)/sum(area))
ggplot(broom::tidy(spTransform(blk_in_bez, wgs84), region='BLK') %>% left_join(sgbII_blk, by=c('id'='BLK'))) + geom_polygon(aes(x=long, y=lat, group=group, fill=sgbIIu65)) + coord_map()

Select relevant data
optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids)
nrow(optim_kids_in_blks)
nrow(optim_kapas)
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)
optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]
dim(optim_matrix)
optim_kapas$Kapa %>% sum
optim_kids_in_blks$kids %>% sum
Naive Zuordnung: Jeder Block zur nächsten Schule
solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup
optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
guides(color=F, fill=F)
Darstellung der Zuordnung als Tabelle
library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
group_by(school) %>% summarise(
kids=sum(kids),
num_blocks=n(),
min_dist=min(min),
avg_dist=mean((kids*avg)/sum(kids)),
max_dist=max(max)
) %>%
inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
mutate(
utilization=kids/Kapa
) %>% select(
Schule=school,
`Blöcke`=num_blocks,
Kapazität=Kapa,
Kinder=kids,
Auslastung=utilization,
`Weg (min)`=min_dist,
`Weg (Ø)`=avg_dist,
`Weg (max)`=max_dist
) %>%
formattable(
list(
Kinder = formatter("span", x ~ digits(x, 2)),
Auslastung = formatter("span",
style = x ~ style(color = ifelse(x < 1, "green", "red")),
x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
),
`Weg (Ø)` = proportion_bar("lightblue"),
`Weg (min)` = proportion_bar("lightblue"),
`Weg (max)` = proportion_bar("lightblue")
)
)
Daten für die App speichern
- entities.geojson
- entities.csv
- units.geojson
- units.csv
- weights.csv
- assignment.csv
Neue Daten
Entities / Schulen
entities = subset(re_schulstand_df, spatial_name %in% relevant_schools) %>%
rename(entity_id = spatial_name) %>%
select(-gml_id, -spatial_alias, -spatial_type) %>%
inner_join(rename(relevant_kapas, capacity=Kapa), by=c('entity_id'='Schulnummer'))
coordinates(entities) = ~ lon + lat
proj4string(entities) = CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
file.remove('app/data/entities.geojson')
entities %>% writeOGR('app/data/entities.geojson', layer="entities", driver="GeoJSON", check_exists=F)
entities@data %>% select(entity_id, capacity) %>% write_csv('app/data/entities.csv')
Units / Statistische Blöcke
units = subset(blk, BEZ == bez_id)
units@data$PLR = NULL
units@data$Einw = NULL
units@data$BEZ = NULL
units@data$unit_id = units@data$BLK
units@data$BLK = NULL
units = units %>% sp::merge(
optim_kids_in_blks %>%
left_join(sgbII_blk) %>%
select(unit_id=BLK, population=kids, sgbIIu65) %>%
)
file.remove('app/data/units.geojson')
units %>% writeOGR('app/data/units.geojson', layer="entities", driver="GeoJSON", check_exists=F)
units@data %>% write_csv('app/data/units.csv')
Zuordnung
assignment = solution %>% select(unit_id=BLK, entity_id=school)
assignment %>% write_csv('app/data/assignment.csv')
Weights / Schulwege
travel_from_blks %>%
rename(unit_id=BLK, entity_id=dst) %>%
#gather(weight, value, -unit_id, -entity_id) %>%
write_csv('app/data/weights.csv')
Zusätzliche Daten
file.copy('download/RBS_OD_BEZ_2015_12.geojson', 'app/data/RBS_OD_BEZ_2015_12.geojson')
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgbGlicywgaW5jbHVkZT1GLCB3YXJuaW5nPUZ9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkobWFwdG9vbHMpCmxpYnJhcnkocmdlb3MpCmBgYAoKRGllc2VzIE5vdGVib29rIGJlcmVpdGV0IGRpZSBEYXRlbiBmw7xyIGRpZSBJbnRlbGxpZ2VudCBab25pbmcgRW5naW5lIHZvci4gRXMgc3BlaWNoZXJ0CgotIGVudGl0aWVzLmdlb2pzb24g4oCUIFNjaHVsZW4sIGRlcmVuIEdlb2tvb3JkaW5hdGVuIHVuZCBBdHRyaWJ1dGU6IGVudGl0eV9pZCwgY2FwYWNpdHksIHVuZCBhbmRlcmUgQXR0cmlidXRlCi0gZW50aXRpZXMuY3N2IOKAlCBTdGF0aXN0aXNjaGUgQmzDtmNrZSBCZXJsaW5zIHVuZCBvcHRpbWllcnVuZ3NyZWxldmFudGUgQXR0cmlidXRlOiBlbnRpdHlfaWQsIGNhcGFjaXR5Ci0gdW5pdHMuZ2VvanNvbiDigJQgU3RhdGlzdGlzY2hlIEJsw7Zja2UgQmVybGlucywgZGVyZW4gR2VvbWV0cmllIHVuZCBBdHRyaWJ1dGUKLSB1bml0cy5jc3Yg4oCUIFN0YXRzaXRpc2NoZSBCbMO2Y2tlIEJlcmxpbnMgdW5kIG9wdGltaWVydW5nc3JlbGV2YW50ZSBBdHRyaWJ1dGU6IHVuaXRfaWQsIHBvcHVsYXRpb24sIHBlcmNlbnRhZ2Vfc2diCi0gd2VpZ2h0cy5jc3Yg4oCUIG9wdGltaWVydW5nc3JlbGV2YW50ZSBHZXdpY2h0ZSB3aWUgRnXDn3dlZ2UgLyBTcGFsdGVuOiBlbnRpdHlfaWQsIHVuaXRfaWQsIHdlaWdodCwgdmFsdWUKLSBhc3NpZ25tZW50LmNzdiDigJQgZWluZSBpbml0aWFsZSBadW9yZG51bmcgLyBTcGFsdGVuOiB1bml0X2lkLCBlbnRpdHlfaWQKCkJlcmVpdHMgaW4gYW5kZXJlbiBzY3JpcHRlbiB3dXJkZSB2b3JiZXJlaXRldDoKCi0gRnXDn3dlZ2VuIHZvbiBlaW5lciBncm/Dn2VuIFNpY2hwcm9iZSB2b24gKF9Xb2huXy0pR2Viw6R1ZGVuIHp1IGFsbGVuIFNjaHVsZW4gd3VyZGVuIGJlcmVjaG5ldCB1bmQgaW4gYHJvdXRlX21hdHJpeC5jc3ZgIGdlc3BlaWNoZXJ0Ci0gRGllIFN0aWNocHJvYmUgd3VyZGUgaW4gYHNhbXBsZWRfYnVpbGRpbmdzLmNzdmAgZ2VzcGVpY2hlcnQKCkRpZSBEYXRlbiB3ZXJkZW4gd2llZm9sZ3Qgdm9yYmVyZWl0ZXQ6CgotIHBybyBCbG9jayB3ZXJkZW4gZGllIEFuemFobCBkZXIgZWluenVzY2h1bGVuZGVuIEtpbmRlciBtaXQgSGlsZmUgZGVyIEVpbndvaG5lcnphaGxlbiBuYWNoIEFsdGVyIGF1ZiBMT1ItRWJlbmUgaW4gYEVXUjIwMTUxMkVfTWF0cml4LmNzdmAgaG9jaGdlcmVjaG5ldAogICAgLSBLaW5kZXIgZGVzIExPUiB3ZXJkZW4gQW50ZWlsaWcgbmFjaCBFaW53b2huZXJ6YWhsIGRlcyBCbG9ja3MgaW0gVmVyaMOkbHRuaXMgenVtIExPUiBhdWYgZGllIEJsw7Zja2UgdmVydGVpbHQKLSBlcyB3ZXJkZW4gbWluaW1hbGUsIGR1cmNoc2Nobml0dGxpY2hlIHVuZCBtYXhpbWFsZSBGdcOfd2VnZSBhdXMgamVkZW0gQmxvY2sgZXJyZWNobmV0CgpUT0RPOgotIGRpZSBzb3ppb8O2a29ub21pc2NoZW4gRmFrdG9yZW4gd2VyZGVuIGF1cyBkZW4gV2FobGJlemlya2VuIGF1ZiBkaWUgQmzDtmNrZSBob2NoZ2VyZWNobmV0IChodHRwczovL2dpdGh1Yi5jb20vYmVybGluZXJtb3JnZW5wb3N0L2NvZ3JhbikKCiMjIExhZGVuIGRlciBEYXRlbgoKYGBge3IgbG9hZCBkYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzYW1wbGVkX2J1aWxkaW5ncyA9IHJlYWRfcmRzKCdvdXRwdXQvc2FtcGxlZF9idWlsZGluZ3MucmRzJykKYmV6ID0gcmVhZE9HUignZG93bmxvYWQvUkJTX09EX0JFWl8yMDE1XzEyLmdlb2pzb24nLCBsYXllciA9ICdPR1JHZW9KU09OJywgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpibGsgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmxvciA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9MT1JfMjAxNV8xMi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKcmVfc2NodWxzdGFuZCA9IHJlYWRPR1IoJ2Rvd25sb2FkL3JlX3NjaHVsc3RhbmQuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKIyMjIFNjaHVsd2VnZQoKYGBge3J9CnJvdXRlX21hdHJpeCA9IHJlYWRfcmRzKCdvdXRwdXQvcm91dGVfbWF0cml4LnJkcycpCmBgYAoKIyMjIFNjaHVsa2FwYXppdMOkdGVuIHVuZCBFaW53b2huZXJ6YWhsZXIgYXVmIExPUi1FYmVuZQoKYGBge3J9CmthcGFzID0gcmVhZF9jc3YoJ2Rvd25sb2FkL2FubWVsZGV6YWhsZW4uY3N2JykgJT4lIGZpbHRlcihncmVwbCgnRycsIFNjaHVsbnVtbWVyKSkgJT4lIGZpbHRlcighaXMubmEoYFBsw6R0emVgKSkKZWlud29obmVyX2xvciA9IHJlYWRfZGVsaW0oJ2Rvd25sb2FkL0VXUjIwMTUxMkVfTWF0cml4LmNzdicsIGRlbGltPSc7JykKYGBgCgojIyDDnGJlcnByw7xmdW5nIGRlciBWb2xsc3TDpG5kaWdrZWl0IGRlciBEYXRlbiDDvGJlciBBbm1lbGRlemFobGVuL0thcGF6aXTDpHRlbgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZShsb249Y29vcmRzLngxLCBsYXQ9Y29vcmRzLngyKQpyZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgbXV0YXRlKEJFWklSSz1lbmMydXRmOChhcy5jaGFyYWN0ZXIoQkVaSVJLKSkpICU+JQogIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKQpgYGAKCkbDvHIgd2VsY2hlIEJlemlya2UgaGFiZW4gd2lyIGbDvHIgYWxsZSBTY2h1bGVuIEthcGF6aXTDpHRlbiBnZWdlYmVuPwoKYGBge3J9CnJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUgZ3JvdXBfYnkoQkVaSVJLKSAlPiUgc3VtbWFyaXNlKGBBbnphaGwgU2NodWxlbmAgPSBuKCkpICU+JQogIHJlbmFtZShCZXppcms9QkVaSVJLKSAlPiUgbGVmdF9qb2luKGthcGFzICU+JSBncm91cF9ieShCZXppcmspICU+JSBzdW1tYXJpc2UoYE1pdCBLYXBheml0w6R0YCA9IG4oKSkpICU+JSBmaWx0ZXIoYEFuemFobCBTY2h1bGVuYCA9PSBgTWl0IEthcGF6aXTDpHRgKQpgYGAKCsOcYmVycHLDvGZ1bmcgb2IgZGllIExpc3RlIGRlciBTY2h1bGVuIHVuZCBMaXN0ZSBkZXIgU2NodWxlbiBtaXQgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIGdsZWljaCBzaW5kOgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKCmBgYHtyfQptYXAgPSBnZXRfbWFwKCdCZXJsaW4nKQpgYGAKCgpgYGB7cn0KcmVfc2NodWxzdGFuZF9kZl93X2thcGFzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgbGVmdF9qb2luKGthcGFzLCBieT1jKCdzcGF0aWFsX25hbWUnPSdTY2h1bG51bW1lcicpKQpgYGAKClBsb3QgYWxsZXIgU2NodWxlbiwgbWl0IGRlciBJbmZvLCBvYiBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gdmVyZsO8Z2JhciBzaW5kLgoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmspICU+JSBtdXRhdGUobWlzc2luZy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9bWlzc2luZy5jYXBhKSwgZGF0YT1kYXRhKSArCiAgICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbiktMC4wMSwgbWF4KGRhdGEkbG9uKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgpGaWx0ZXIgYXVmIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiAoZsO8ciBULVMgc2luZCBkYXMgYWxsZSk6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGZfd19rYXBhcyAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIGZpbHRlcihCRVpJUks9PWJlemlyayAmICFpcy5uYShgUGzDpHR6ZWApKSAlPiUgLiRzcGF0aWFsX25hbWUKcmVsZXZhbnRfc2Nob29scwpgYGAKCiMjIE1hcHBpbmcgQmV6aXJrLT5MT1ItPkJsb2NrCgpgYGB7cn0KZGZfYmV6ID0gYXMuZGF0YS5mcmFtZShiZXopCmRmX2xvciA9IGFzLmRhdGEuZnJhbWUobG9yKQpkZl9ibGsgPSBhcy5kYXRhLmZyYW1lKGJsaykKYGBgCgojIyMgU2FuaXR5LUNoZWNrOiBMT1JzIHVuZCBCbMO2Y2tlIGltIEJlemlyawoKYGBge3J9CmJlel9pZCA9IGZpbHRlcihkZl9iZXosIEJFWk5BTUUgPT0gYmV6aXJrKSRCRVoKcmVsZXZhbnRfbG9ycyA9IGRmX2xvciAlPiUgZmlsdGVyKEJFWiA9PSBiZXpfaWQpCnJlbGV2YW50X2Jsa3MgPSBkZl9ibGsgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWxvcltsb3IkQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6LCBjb2xvcj0ncmVkJykKYGBgCgojIyMgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpnZ3Bsb3QoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJsa1tibGskQkVaID09IGJlel9pZCxdKSArIGNvb3JkX21hcCgpICsgZ2VvbV9wYXRoKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGRhdGE9YmV6W2JleiRCRVogPT0gYmV6X2lkLF0sIGNvbG9yPSdyZWQnKQpgYGAKCiMjIEtpbmRlciBpbSBCZXppcmsgYXVmIEJsw7Zja2UgaG9jaHJlY2huZW4KCsOcYmVyIGRpZSBFaW53b2huZXJpbmZvcm1hdGlvbmVuIGluIGBSQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbmAga2FubiBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YCB2b24gTE9SLUViZW5lIGF1ZiBCbG9ja2ViZW5lIGhvY2hnZXJlY2huZXQgd2VyZGVuLgoKVE9ETzogU3RhdHRkZXNzZW4gbWl0IGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJsaW5lcm1vcmdlbnBvc3QvY29ncmFuIG1hY2hlbj8KCiMjIyBQbG90IGRlciA2LUrDpGhyaWdlbiBuYWNoIGBFV1IyMDE1MTJFX01hdHJpeC5jc3ZgCgpXaXIgdmVyd2VuZGVuIGRhcyBtaXR0ZWwgZGVyIDUtIHVuZCA2LUrDpGhyaWdlbi4KClRPRE8gbmV1ZSBEYXRlbiB2b24gVG9ycmVzPwpUT0RPIFByb2dub3NlPwoKYGBge3J9CnJlbGV2YW50X2V3ciA9IGVpbndvaG5lcl9sb3IgJT4lCiAgc2VsZWN0KFJBVU1JRCwgRV9FMDVfMDYsIEVfRTA2XzA3KSAlPiUKICBmaWx0ZXIoUkFVTUlEICVpbiUgcmVsZXZhbnRfbG9ycyRQTFIpICU+JQogICMgU2Nobml0dCBkZXIgNSB1bmQgNi1Kw6RocmlnZW4KICBtdXRhdGUoa2lkcz0oYXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDZfMDcpKSthcy5udW1lcmljKGdzdWIoJywnLCcuJyxFX0UwNV8wNikpKS8yKSAlPiUgYXMuZGF0YS5mcmFtZSgpCgpkYXRhID0gdGlkeShsb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdQTFInKSAlPiUgaW5uZXJfam9pbihyZWxldmFudF9ld3IsIGJ5PWMoJ2lkJz0nUkFVTUlEJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIyBQbG90IGRlciBFaW53b2huZXIgYXVmIEJsb2NrZWJlbmUKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihkZl9ibGssIGJ5PWMoJ2lkJz0nQkxLJykpICU+JSBtdXRhdGUoRWludz1pZmVsc2UoRWludz09MCwgTkEsIEVpbncpKQowCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPUVpbncpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgojIyMgSG9jaHJyZWNobnVuZyBhdWYgQmzDtmNrZSwgU3RydWt0dXJxdW90ZQoKYGBge3J9ClN0cnVrdHVycXVvdGUgPSAwLjkKCmtpZHNfaW5fYmxrcyA9IHJlbGV2YW50X2Jsa3MgJT4lCiAgZ3JvdXBfYnkoUExSKSAlPiUKICBtdXRhdGUoRWlud1JhdGlvID0gRWludy9zdW0oRWludykpICU+JQogIHVuZ3JvdXAgJT4lCiAgbGVmdF9qb2luKHJlbGV2YW50X2V3ciwgYnk9YygnUExSJz0nUkFVTUlEJykpICU+JQogIG11dGF0ZShraWRzID0gRWlud1JhdGlvKmtpZHMpICU+JQogIG11dGF0ZShraWRzID0gU3RydWt0dXJxdW90ZSpraWRzKSAlPiUgIyBTdHJ1a3R1cnF1b3RlCiAgc2VsZWN0KEJFWiwgUExSLCBCTEssIEVpbncsIGtpZHMpICU+JQogIGFzLmRhdGEuZnJhbWUoKQpyb3cubmFtZXMoa2lkc19pbl9ibGtzKSA9IGtpZHNfaW5fYmxrcyRCTEsKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBpbm5lcl9qb2luKGtpZHNfaW5fYmxrcywgYnk9YygnaWQnPSdCTEsnKSkgJT4lIG11dGF0ZShraWRzPWlmZWxzZShraWRzPT0wLCBOQSwga2lkcyksIEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIFZlcmbDvGdiYXJlIFBsw6R0emUKCmBgYHtyfQpyZWxldmFudF9rYXBhcyA9IGthcGFzICU+JSBzZWxlY3QoU2NodWxudW1tZXIsIEthcGE9YFBsw6R0emVgKSAlPiUgZmlsdGVyKFNjaHVsbnVtbWVyICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lIGFzLmRhdGEuZnJhbWUoKQojcm93Lm5hbWVzKHJlbGV2YW50X2thcGFzKSA9IHJlbGV2YW50X2thcGFzJFNjaHVsbnVtbWVyCmBgYAoKIyMjIMOcYmVycHLDvGZ1bmcgZGVyIFN1bW1lIGRlciBLYXBheml0w6R0ZW4sIEFubWVsZHVuZ2VuIHVuZCBLaW5kZXJzdGF0aXN0aWtlbgoKYGBge3J9CidTdW1tZSBLYXBhcycKcmVsZXZhbnRfa2FwYXMgJT4lIC4kS2FwYSAlPiUgc3VtCidBbm1lbGR1bmdlbicKa2FwYXMgJT4lIG11dGF0ZShBbm1lbGR1bmdlbiA9IGFzLm51bWVyaWMoZ3N1YignW14wLTldJywgJycsIEFubWVsZHVuZ2VuKSkpICU+JSBmaWx0ZXIoU2NodWxudW1tZXIgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUgLiRBbm1lbGR1bmdlbiAlPiUgc3VtCidLaWRzIGxhdXQgU3RhdGlzdGlrJwpraWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCnJlbGV2YW50X2V3ciRraWRzICU+JSBzdW0KYGBgCgojIyBTY2h1bHdlZ2Ugdm9uIEJsw7Zja2VuIHp1IFNjaHVsZW4gYWdncmVnaWVyZW4KCkbDvHIgamVkZXMgV29obmdlYsOkdWRlIHN1Y2hlbiB3aXIgZGVuIHp1Z2Vow7ZyaWdlbiBCbG9jawoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBsZWZ0X2pvaW4ocm91dGVfbWF0cml4ICU+JSBmaWx0ZXIoZHN0ICVpbiUgcmVsZXZhbnRfc2Nob29scyksIGJ5PWMoJ09JJz0nc3JjJykpCmhlYWQocm91dGVzX2Zyb21fYmxrcykKYGBgCgojIyMgUGxvdCBkZXIgcmVsZXZhbnRlbiBCbMO2Y2tlIChtaXQgV29obmdlYsOkdWRlbikKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihyb3V0ZXNfZnJvbV9ibGtzICU+JSBncm91cF9ieShCTEspICU+JSBzdW1tYXJpc2Uobj1uKCkpLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9ZGF0YSkgKwogICNnZW9tX3BvaW50KGFlcyh4PWxvbiwgeT1sYXQpLCBkYXRhPXJiX2RmLCBjb2xvcj0nYmxhY2snLCBzaXplPTAuMDEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyA9IHJvdXRlc19mcm9tX2Jsa3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZ3JvdXBfYnkoQkxLLCBkc3QpICU+JSBzdW1tYXJpc2UobWluPW1pbihkaXN0YW5jZSksIGF2Zz1tZWFuKGRpc3RhbmNlKSwgbWVkPW1lZGlhbihkaXN0YW5jZSksIG1heD1tYXgoZGlzdGFuY2UpKSAlPiUgdW5ncm91cAp0cmF2ZWxfZnJvbV9ibGtzCmBgYAoKIyMjIFBsb3QgZGVyIEJsw7Zja2UgbWl0IEbDpHJidW5nIG5hY2ggZHVyY2hzY2huaXR0bGljaGVtIFdlZyB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIyBTb3ppb8O2a29ub21pc2NoZSBEYXRlbgoKV2lyIGhhYmVuIFNvemlvw7Zrb25vbWlzY2hlIERhdGVuIGluIGRlbiBXYWhsYmV6aXJrZW4uIFN0cmF0ZWdpZToKLSBTY2huZWlkZW4gZGVyIFdhaGxiZXppcmtlIG1pdCBkZW4gQmzDtmNrZW4KLSDDnGJlcm5haG1lIGRlcyBQcm96ZW50d2VydGVzIHZvbSBXYWhsYmV6aXJrIGbDvHIgamVkZW4gKFVudGVyLSlibG9jawotIFp1b3JkbnVuZyB6dSBqZWRlbSBCbG9jayB1bmQgVmVyZWluaWd1bmcgZHVyY2ggRmzDpGNoZW4vV29obmhhdXMtZ2V3aWNodGV0ZXMgTWl0dGVsIGRlcyBQcm96ZW50d2VydGVzIAoKYGBge3J9ClVXQiA9IHJlYWRPR1IoJ2Rvd25sb2FkL1JCU19PRF9VV0JfQUdIMjAxNi5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKVVdCJElEID0gcGFzdGUwKFVXQiRCRVosIFVXQiRVV0IpCnNvemlvX1VXQiA9IHJlYWRfZXhjZWwoJ2Rvd25sb2FkL0RMX0JFX0FIMjAxNl9TdHJ1a3R1cmRhdGVuLnhsc3gnLCBzaGVldCA9IDMpICU+JSBzZWxlY3QoSUQsIHNnYklJdTY1PWBFaW53b2huZXIgdW50ZXIgNjUgaW4gU0dCIElJIDIwMTQgUHJvemVudGApClVXQiA9IFVXQiAlPiUgc3A6Om1lcmdlKHNvemlvX1VXQiwgYnk9J0lEJykKCmdncGxvdChicm9vbTo6dGlkeShVV0IsIHJlZ2lvbj0nSUQnKSAlPiUgaW5uZXJfam9pbihVV0IgJT4lIGFzLmRhdGEuZnJhbWUsIGJ5PWMoJ2lkJz0nSUQnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgpDaGVjayBmb3Igc2VsZiBpbnRlcnNlY3Rpb25zCmBgYHtyfQp3Z3M4NCA9IENSUyhwcm9qNHN0cmluZyhibGspKQplYV9wcm9qZWN0aW9uID0gQ1JTKCIrcHJvaj1sYWVhICtsYXRfMD01MiArbG9uXzA9MTAgK3hfMD00MzIxMDAwICt5XzA9MzIxMDAwMCArZWxscHM9R1JTODAgK3Rvd2dzODQ9MCwwLDAsMCwwLDAsMCArdW5pdHM9bSArbm9fZGVmcyIpCmJsa19pbl9iZXogPSBibGtbYmxrJEJFWiA9PSAnMDcnLF0KZ0lzVmFsaWQoYmxrX2luX2JleikKYmxrX2luX2JleiA9IGdCdWZmZXIoc3BUcmFuc2Zvcm0oYmxrX2luX2JleiwgZWFfcHJvamVjdGlvbiksIGJ5aWQ9VCwgd2lkdGggPSAtMC4xKQphbnkoeG9yKGdJbnRlcnNlY3RzKGJ1ZmZlcmVkX2Jsa19pbl9iZXosIGJ5aWQ9VCwgcmV0dXJuRGVuc2UgPSBUKSwgZGlhZygxLCBsZW5ndGgoYmxrX2luX2JleikpID09IDEpKQpwbG90KGJsa19pbl9iZXopCiNnSW50ZXJzZWN0cyhVV0JbVVdCJEJFWiA9PSAnMDcnLF0sIGJ5aWQ9VFJVRSkKYGBgCgpgYGB7cn0KdXdiX2luX2JleiA9IGdCdWZmZXIoc3BUcmFuc2Zvcm0oVVdCW1VXQiRCRVogPT0gJzA3JyxdLCBlYV9wcm9qZWN0aW9uKSwgYnlpZD1ULCB3aWR0aD0tMC4xKQpnSXNWYWxpZCh1d2JfaW5fYmV6KQphbnkoeG9yKGdJbnRlcnNlY3RzKHV3Yl9pbl9iZXosIGJ5aWQ9VCwgcmV0dXJuRGVuc2UgPSBUKSwgZGlhZygxLCBsZW5ndGgodXdiX2luX2JleikpID09IDEpKQpwbG90KHV3Yl9pbl9iZXopCmBgYAoKYGBge3J9CmludGVyc2VjdGlvbiA9IGdJbnRlcnNlY3Rpb24odXdiX2luX2JleiwgYmxrX2luX2JleiwgYnlpZCA9IFQsIGRyb3BfbG93ZXJfdGQgPSBUKQoKZ0lzVmFsaWQoaW50ZXJzZWN0aW9uKQpwbG90KGludGVyc2VjdGlvbikKYGBgCgpgYGB7cn0KaW50ZXJzZWN0aW9uX3V3Yl9kYXRhID0gaW50ZXJzZWN0aW9uICU+JSBvdmVyKHV3Yl9pbl9iZXopCmludGVyc2VjdGlvbl9ibGtfZGF0YSA9IGludGVyc2VjdGlvbiAlPiUgb3ZlcihibGtfaW5fYmV6KQppbnRlcnNlY3Rpb25fYXJlYSA9IGdBcmVhKGludGVyc2VjdGlvbiwgYnlpZD1UKQppbnRlcnNlY3Rpb25fZGF0YSA9IGNiaW5kKAogICAgaW50ZXJzZWN0aW9uX3V3Yl9kYXRhICU+JSBzZWxlY3QoVVdCX0lEPUlELCBzZ2JJSXU2NSksCiAgICBpbnRlcnNlY3Rpb25fYmxrX2RhdGEgJT4lIHNlbGVjdChCTEssIEJMS19FaW53PUVpbncpLAogICAgZGF0YS5mcmFtZShhcmVhPWludGVyc2VjdGlvbl9hcmVhKSAjIEZJWE1FIGhvdyBlbHNlIHRvIG5vcm1hbGl6ZT8KICAgICkKYGBgCgpgYGB7cn0KbGVuZ3RoKGludGVyc2VjdGlvbikKbnJvdyhibGtfaW5fYmV6KQojIGRpZCB3ZSBtaXNzIGFueSBibG9ja3M/CnNldGRpZmYoYmxrX2luX2JleiRCTEssIGludGVyc2VjdGlvbl9kYXRhJEJMSykKYGBgCgpQcm8gQmxvY2sgbWlzY2hlIGRpZSBTR0ItV2VydGUgZGVyIFVudGVyYmzDtmNrZSBnZXdpY2h0ZXQgbmFjaCBGbMOkY2hlLgpGSVhNRSAtIGJlc3NlciBuYWNoIEFuemFobCBkZXIgV29obmjDpHVzZXI/CmBgYHtyfQpzZ2JJSV9ibGsgPSBpbnRlcnNlY3Rpb25fZGF0YSAlPiUKICBncm91cF9ieShCTEspICU+JQogIHN1bW1hcmlzZShzZ2JJSXU2NT1zdW0oc2diSUl1NjUqYXJlYSkvc3VtKGFyZWEpKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGJyb29tOjp0aWR5KHNwVHJhbnNmb3JtKGJsa19pbl9iZXosIHdnczg0KSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNnYklJX2JsaywgYnk9YygnaWQnPSdCTEsnKSkpICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1zZ2JJSXU2NSkpICsgY29vcmRfbWFwKCkKYGBgCgoKIyMgU2VsZWN0IHJlbGV2YW50IGRhdGEKCmBgYHtyfQpvcHRpbV9rYXBhcyA9IHJlbGV2YW50X2thcGFzCm9wdGltX2tpZHNfaW5fYmxrcyA9IGtpZHNfaW5fYmxrcyAlPiUgZmlsdGVyKGtpZHMgPiAwKSAlPiUgaW5uZXJfam9pbih0cmF2ZWxfbWF0cml4LCBieT0nQkxLJykgJT4lIHNlbGVjdChCTEssIGtpZHMpICU+JSBtdXRhdGUoa2lkcz1raWRzKQpucm93KG9wdGltX2tpZHNfaW5fYmxrcykKbnJvdyhvcHRpbV9rYXBhcykKCnNlbGVjdF9zY2hvb2xzID0gYXMuY2hhcmFjdGVyKG9wdGltX2thcGFzJFNjaHVsbnVtbWVyKQpzZWxlY3RfYmxrcyA9IGFzLmNoYXJhY3RlcihvcHRpbV9raWRzX2luX2Jsa3MkQkxLKQoKb3B0aW1fbWF0cml4ID0gaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIHRyYXZlbF9tYXRyaXgsIGJ5PSdCTEsnKVtzZWxlY3Rfc2Nob29sc10KCmRpbShvcHRpbV9tYXRyaXgpCgpvcHRpbV9rYXBhcyRLYXBhICU+JSBzdW0Kb3B0aW1fa2lkc19pbl9ibGtzJGtpZHMgJT4lIHN1bQpgYGAKCiMjIE5haXZlIFp1b3JkbnVuZzogSmVkZXIgQmxvY2sgenVyIG7DpGNoc3RlbiBTY2h1bGUKCmBgYHtyfQpzb2x1dGlvbiA9IG9wdGltX21hdHJpeCAlPiUgbXV0YXRlKEJMSz1vcHRpbV9raWRzX2luX2Jsa3MkQkxLKSAlPiUgZ2F0aGVyKHNjaG9vbCwgZGlzdCwgLUJMSykgJT4lIGdyb3VwX2J5KEJMSykgJT4lIHRvcF9uKDEsIC1kaXN0KSAlPiUgdW5ncm91cAoKb3B0aW1fbWF0cml4ICU+JSB0ICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzdW1tYXJpc2VfZWFjaChmdW5zKG1pbikpICU+JSBzdW0oKQoKc29saW5lcyA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSAlPiUgaW5uZXJfam9pbihjYmluZChhcy5kYXRhLmZyYW1lKGNvb3JkaW5hdGVzKGJsa1tibGskQkVaID09IGJlel9pZCxdKSksIGJsa1tibGskQkVaID09IGJlel9pZCxdQGRhdGFbJ0JMSyddKSkKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4oc29sdXRpb24sIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCwgZGFya2VuID0gYygwLjUsICd3aGl0ZScpKSArIGdlb21fcG9seWdvbihhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXAsIGZpbGw9c2Nob29sKSwgZGF0YT1kYXRhKSArCiAgZ2VvbV9zZWdtZW50KGFlcyh4PVYxLHk9VjIseGVuZD1sb24seWVuZD1sYXQpLCBkYXRhPXNvbGluZXMsIHNpemU9MC4zKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQpLCBjb2xvcj0nYmxhY2snLCBzaXplPTIsIGRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9c3BhdGlhbF9uYW1lKSwgZGF0YSA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGlubmVyX2pvaW4oc29sdXRpb24sIGJ5PWMoJ3NwYXRpYWxfbmFtZSc9J3NjaG9vbCcpKSkgKwogIGNvb3JkX21hcCh4bGltPWMobWluKGRhdGEkbG9uZyktMC4wMSwgbWF4KGRhdGEkbG9uZykrMC4wMSksIHlsaW09YyhtaW4oZGF0YSRsYXQpLTAuMDEsIG1heChkYXRhJGxhdCkrMC4wMSkpICsKICBndWlkZXMoY29sb3I9RiwgZmlsbD1GKQpgYGAKCiMjIERhcnN0ZWxsdW5nIGRlciBadW9yZG51bmcgYWxzIFRhYmVsbGUKCmBgYHtyfQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQpgYGAKCmBgYHtyfQpzb2x1dGlvbiAlPiUgaW5uZXJfam9pbihvcHRpbV9raWRzX2luX2Jsa3MsIGJ5PSdCTEsnKSAlPiUgaW5uZXJfam9pbih0cmF2ZWxfZnJvbV9ibGtzLCBieT1jKCdCTEsnPSdCTEsnLCAnc2Nob29sJz0nZHN0JykpICU+JQogIGdyb3VwX2J5KHNjaG9vbCkgJT4lIHN1bW1hcmlzZSgKICAgIGtpZHM9c3VtKGtpZHMpLAogICAgbnVtX2Jsb2Nrcz1uKCksCiAgICBtaW5fZGlzdD1taW4obWluKSwKICAgIGF2Z19kaXN0PW1lYW4oKGtpZHMqYXZnKS9zdW0oa2lkcykpLAogICAgbWF4X2Rpc3Q9bWF4KG1heCkKICApICU+JQogIGlubmVyX2pvaW4ocmVsZXZhbnRfa2FwYXMsIGJ5PWMoJ3NjaG9vbCc9J1NjaHVsbnVtbWVyJykpICU+JQogIG11dGF0ZSgKICAgIHV0aWxpemF0aW9uPWtpZHMvS2FwYQogICkgJT4lIHNlbGVjdCgKICAgU2NodWxlPXNjaG9vbCwKICAgYEJsw7Zja2VgPW51bV9ibG9ja3MsCiAgIEthcGF6aXTDpHQ9S2FwYSwKICAgS2luZGVyPWtpZHMsCiAgIEF1c2xhc3R1bmc9dXRpbGl6YXRpb24sCiAgIGBXZWcgKG1pbilgPW1pbl9kaXN0LAogICBgV2VnICjDmClgPWF2Z19kaXN0LAogICBgV2VnIChtYXgpYD1tYXhfZGlzdAogICkgJT4lCiAgZm9ybWF0dGFibGUoCiAgICBsaXN0KAogICAgICBLaW5kZXIgPSBmb3JtYXR0ZXIoInNwYW4iLCB4IH4gZGlnaXRzKHgsIDIpKSwKICAgICAgQXVzbGFzdHVuZyA9IGZvcm1hdHRlcigic3BhbiIsCiAgICAgICAgc3R5bGUgPSB4IH4gc3R5bGUoY29sb3IgPSBpZmVsc2UoeCA8IDEsICJncmVlbiIsICJyZWQiKSksCiAgICAgICAgeCB+IGljb250ZXh0KGlmZWxzZSh4IDwgMSwgIm9rIiwgInJlbW92ZSIpLCBwZXJjZW50KHgpKQogICAgICApLAogICAgICBgV2VnICjDmClgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpLAogICAgICBgV2VnIChtaW4pYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKSwKICAgICAgYFdlZyAobWF4KWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIikKICAgICkKICApCmBgYAoKCiMjIERhdGVuIGbDvHIgZGllIEFwcCBzcGVpY2hlcm4KCi0gZW50aXRpZXMuZ2VvanNvbgotIGVudGl0aWVzLmNzdgotIHVuaXRzLmdlb2pzb24KLSB1bml0cy5jc3YKLSB3ZWlnaHRzLmNzdgotIGFzc2lnbm1lbnQuY3N2CgojIyMgTmV1ZSBEYXRlbgoKCiMjIyMgRW50aXRpZXMgLyBTY2h1bGVuCgpgYGB7cn0KZW50aXRpZXMgPSBzdWJzZXQocmVfc2NodWxzdGFuZF9kZiwgc3BhdGlhbF9uYW1lICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lCiAgcmVuYW1lKGVudGl0eV9pZCA9IHNwYXRpYWxfbmFtZSkgJT4lCiAgc2VsZWN0KC1nbWxfaWQsIC1zcGF0aWFsX2FsaWFzLCAtc3BhdGlhbF90eXBlKSAlPiUKICBpbm5lcl9qb2luKHJlbmFtZShyZWxldmFudF9rYXBhcywgY2FwYWNpdHk9S2FwYSksIGJ5PWMoJ2VudGl0eV9pZCc9J1NjaHVsbnVtbWVyJykpCmNvb3JkaW5hdGVzKGVudGl0aWVzKSA9IH4gbG9uICsgbGF0CnByb2o0c3RyaW5nKGVudGl0aWVzKSA9IENSUygiK3Byb2o9bG9uZ2xhdCArZWxscHM9V0dTODQgK2RhdHVtPVdHUzg0ICtub19kZWZzIikKZmlsZS5yZW1vdmUoJ2FwcC9kYXRhL2VudGl0aWVzLmdlb2pzb24nKQplbnRpdGllcyAlPiUgd3JpdGVPR1IoJ2FwcC9kYXRhL2VudGl0aWVzLmdlb2pzb24nLCBsYXllcj0iZW50aXRpZXMiLCBkcml2ZXI9Ikdlb0pTT04iLCBjaGVja19leGlzdHM9RikKZW50aXRpZXNAZGF0YSAlPiUgc2VsZWN0KGVudGl0eV9pZCwgY2FwYWNpdHkpICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL2VudGl0aWVzLmNzdicpCmBgYAoKIyMjIyBVbml0cyAvIFN0YXRpc3Rpc2NoZSBCbMO2Y2tlCgpgYGB7cn0KdW5pdHMgPSBzdWJzZXQoYmxrLCBCRVogPT0gYmV6X2lkKQoKdW5pdHNAZGF0YSRQTFIgPSBOVUxMCnVuaXRzQGRhdGEkRWludyA9IE5VTEwKdW5pdHNAZGF0YSRCRVogPSBOVUxMCnVuaXRzQGRhdGEkdW5pdF9pZCA9IHVuaXRzQGRhdGEkQkxLCnVuaXRzQGRhdGEkQkxLID0gTlVMTAoKdW5pdHMgPSB1bml0cyAlPiUgc3A6Om1lcmdlKAogIG9wdGltX2tpZHNfaW5fYmxrcyAlPiUKICAgIGxlZnRfam9pbihzZ2JJSV9ibGspICU+JQogICAgc2VsZWN0KHVuaXRfaWQ9QkxLLCBwb3B1bGF0aW9uPWtpZHMsIHNnYklJdTY1KSAlPiUKICApCgpmaWxlLnJlbW92ZSgnYXBwL2RhdGEvdW5pdHMuZ2VvanNvbicpCnVuaXRzICU+JSB3cml0ZU9HUignYXBwL2RhdGEvdW5pdHMuZ2VvanNvbicsIGxheWVyPSJlbnRpdGllcyIsIGRyaXZlcj0iR2VvSlNPTiIsIGNoZWNrX2V4aXN0cz1GKQp1bml0c0BkYXRhICU+JSB3cml0ZV9jc3YoJ2FwcC9kYXRhL3VuaXRzLmNzdicpCmBgYAoKIyMjIyBadW9yZG51bmcKCmBgYHtyfQphc3NpZ25tZW50ID0gc29sdXRpb24gJT4lIHNlbGVjdCh1bml0X2lkPUJMSywgZW50aXR5X2lkPXNjaG9vbCkKYXNzaWdubWVudCAlPiUgd3JpdGVfY3N2KCdhcHAvZGF0YS9hc3NpZ25tZW50LmNzdicpCmBgYAoKIyMjIyBXZWlnaHRzIC8gU2NodWx3ZWdlCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyAlPiUKICByZW5hbWUodW5pdF9pZD1CTEssIGVudGl0eV9pZD1kc3QpICU+JQogICNnYXRoZXIod2VpZ2h0LCB2YWx1ZSwgLXVuaXRfaWQsIC1lbnRpdHlfaWQpICU+JQogIHdyaXRlX2NzdignYXBwL2RhdGEvd2VpZ2h0cy5jc3YnKQpgYGAKCiMjIyMgWnVzw6R0emxpY2hlIERhdGVuCgpgYGB7cn0KZmlsZS5jb3B5KCdkb3dubG9hZC9SQlNfT0RfQkVaXzIwMTVfMTIuZ2VvanNvbicsICdhcHAvZGF0YS9SQlNfT0RfQkVaXzIwMTVfMTIuZ2VvanNvbicpCmBgYAoK